Skip to content

feat: add opt-in real nickname controls#8713

Open
Sisyphbaous-DT-Project wants to merge 7 commits into
AstrBotDevs:masterfrom
Sisyphbaous-DT-Project:feat/real-nickname-controls
Open

feat: add opt-in real nickname controls#8713
Sisyphbaous-DT-Project wants to merge 7 commits into
AstrBotDevs:masterfrom
Sisyphbaous-DT-Project:feat/real-nickname-controls

Conversation

@Sisyphbaous-DT-Project

@Sisyphbaous-DT-Project Sisyphbaous-DT-Project commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

在群聊场景中,当前用户识别信息里的 nickname 通常来自群昵称或群名片。这个值更适合作为群内展示名,但它可能被频繁修改,也可能和用户真实昵称不一致。对依赖用户识别、长期上下文或个性化回复的场景来说,模型只能看到群昵称会影响身份信息的一致性。

本次改动增加两个默认关闭的真实昵称相关开关,让部署者可以按需选择“追加真实昵称”或“仅使用真实昵称”。未启用新开关时,现有行为保持不变。

同时,本次改动将用户和群组身份信息整理为结构化 JSON 元数据,并对昵称、真实昵称、群名等外部输入做基础清洗,减少控制字符、零宽字符和伪造标签边界对提示词结构的干扰。

Modifications / 改动点

  • 新增 provider_settings.real_nickname_display 开关,开启后在支持的平台上向模型额外提供用户真实昵称。
  • 新增 provider_settings.real_nickname_only 开关,开启后模型只看到真实昵称;当真实昵称不可用或清洗后为空时,回退到原昵称。
  • 将用户身份信息和群组信息改为 JSON 风格元数据,减少自然语言拼接带来的歧义,例如 User metadata: {"user_id":"...","nickname":"..."}
  • 对昵称、真实昵称和群名进行元数据清洗,包括控制字符、零宽字符、尖括号边界和长度限制。
  • 补充 WebUI 配置项、三种语言的配置文案,以及条件配置项出现/消失时的轻量过渡动画。
  • 补充单元测试,覆盖默认行为、真实昵称追加、仅真实昵称模式、缺失回退、清洗、去重和对象形态 raw_message 读取。
image image
  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

已执行以下验证:

PYTHONPATH=. pytest -s tests/unit/test_astr_main_agent.py -q
99 passed, 1 warning

npm run typecheck
vue-tsc --noEmit

python -m json.tool dashboard/src/i18n/locales/zh-CN/features/config-metadata.json >/dev/null
python -m json.tool dashboard/src/i18n/locales/en-US/features/config-metadata.json >/dev/null
python -m json.tool dashboard/src/i18n/locales/ru-RU/features/config-metadata.json >/dev/null

git diff --check

本地手动验证:

  • 启动 AstrBot 后端和本地 WebUI 后,访问 http://localhost:3000/#/config#normal
  • 开启“用户识别”后,“追加用户真实昵称”配置项按条件出现。
  • 开启“追加用户真实昵称”后,“仅使用真实昵称”配置项按条件出现,并带有过渡动画。
  • 保存配置后,后端可读取新增配置项并按预期生成用户元数据。

Checklist / 检查清单

  • 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
  • 我的更改经过了测试,并已在上方提供了验证步骤和测试结果。
  • 我确保没有引入新依赖库。
  • 我的更改没有引入恶意代码。

Summary by Sourcery

Add configurable support for exposing users’ real nicknames and structured metadata in system reminders, along with UI and test updates.

New Features:

  • Introduce optional provider settings to append and optionally prefer real user nicknames in identifier metadata.
  • Expose group and user identity as structured JSON-style metadata in system reminders instead of plain text.

Enhancements:

  • Sanitize user and group metadata values to strip control/zero-width characters, normalize whitespace and brackets, and cap length before injecting into prompts.
  • Add animated transitions for conditionally shown object-type configuration items in the Web UI, respecting reduced-motion preferences.

Tests:

  • Extend main agent tests to cover real nickname display/only modes, fallbacks, metadata sanitization, de-duplication, and object-shaped raw message handling.

新增用户真实昵称展示相关配置开关,支持在用户识别元数据中追加真实昵称,或在开启后仅向模型提供真实昵称。

将用户和群组身份信息改为结构化 JSON 元数据,并对昵称、群名等外部输入进行清洗,降低控制字符、零宽字符和伪造标签边界对提示词结构的干扰。

补充 WebUI 配置文案与条件项过渡动画,并增加单元测试覆盖默认行为、真实昵称回退、清洗、去重和对象形态 raw_message 读取。
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. area:core The bug / feature is about astrbot's core, backend area:webui The bug / feature is about webui(dashboard) of astrbot. feature:persona The bug / feature is about astrbot AI persona system (system prompt) labels Jun 10, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces features to append and display a user's real nickname in system reminders, along with robust metadata sanitization and formatting. Key changes include adding helper functions to clean metadata values (removing control characters, zero-width characters, and escaping HTML-like tags), updating the configuration schema and localized strings, and wrapping configuration items in the dashboard with a TransitionGroup for smoother UI transitions. Unit tests were also added to verify the new metadata processing and fallback behaviors. Feedback on the changes highlights a potential layout issue in the dashboard transition styles: hardcoding max-height to 180px in the TransitionGroup styles can cause items taller than 180px to be clipped or experience sudden height jumps during transitions. It is recommended to remove the max-height transition and only animate opacity and transform to ensure compatibility with varying item heights.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread dashboard/src/components/shared/AstrBotConfigV4.vue Outdated

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • In _append_system_reminders, real-nickname de-duplication currently compares real_nickname (unsanitized) against sanitized_user_nickname; if you want to ensure zero-width/control-character differences don’t cause redundant metadata, consider sanitizing real_nickname before the equality check and reuse that sanitized value in _format_metadata.
  • In AstrBotConfigV4.vue, createSelectorModel(itemKey).value is called multiple times per item (including inside v-ifs and v-for), which can lead to unnecessary recomputation; you could cache the model per item (e.g., via a computed or local variable inside the loop) to avoid repeated calls.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_append_system_reminders`, real-nickname de-duplication currently compares `real_nickname` (unsanitized) against `sanitized_user_nickname`; if you want to ensure zero-width/control-character differences don’t cause redundant metadata, consider sanitizing `real_nickname` before the equality check and reuse that sanitized value in `_format_metadata`.
- In `AstrBotConfigV4.vue`, `createSelectorModel(itemKey).value` is called multiple times per item (including inside `v-if`s and `v-for`), which can lead to unnecessary recomputation; you could cache the model per item (e.g., via a computed or local variable inside the loop) to avoid repeated calls.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

移除条件配置项过渡动画中的 max-height 动画,避免较高配置项在进入或离开过渡时被裁切或出现高度跳变。
@Sisyphbaous-DT-Project

Sisyphbaous-DT-Project commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

关于 Sourcery 的两点建议,已逐项确认:

  • _append_system_reminders 中的 real_nickname 在参与去重比较前已经通过 _sanitize_optional_metadata_value() 清洗,因此这里不是未清洗值和清洗值比较的问题;相关逻辑保持不变。
  • createSelectorModel(itemKey).value 的重复调用属于现有通用配置渲染器的模板写法,当前场景开销很小。为避免扩大本 PR 范围,暂不在这个改动里重构该部分。

补充:关于 Gemini 对 max-height 过渡的建议,后续实测发现移除该过渡会导致条件配置项关闭时直接消失,缺少折叠收起动画。因此已在 5c4fa2a 中撤销 caa2c64,恢复原有折叠高度过渡。当前 PR 暂不扩大为动态高度动画重构;如果后续需要兼容更高配置项,可以在单独改动中设计更完整的动态高度过渡方案。

已重新执行 npm run typecheckgit diff --check

撤销此前移除 max-height 过渡的调整。该调整会让条件配置项关闭时直接消失,缺少折叠收起动画;恢复原有高度过渡以保持展开和收起体验一致。
@Dt8333

Dt8333 commented Jun 11, 2026

Copy link
Copy Markdown
Member
  • 补充 WebUI 配置项、三种语言的配置文案,以及条件配置项出现/消失时的轻量过渡动画。

有关这个轻量过渡动画,是否与这个PR关联不大?
如果这个PR对这个过渡动画没什么强需求,建议拆到另一个PR,方便大伙审核代码。

@Sisyphbaous-DT-Project

Sisyphbaous-DT-Project commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

有关这个轻量过渡动画,是否与这个PR关联不大?

嗯,要说和功能呢其实关联不大,只是我设计的时候希望让新功能在日常不开启用户识别的情况下会被折叠,只有开启后才会展开,这样更加符合直觉也不需要更多的hint来介绍,过渡动画是为了可以让展开更加优美,不然只剩下硬切,体验上会差一点,不过我可以试试把动画提交成另外一个 PR ?

@Sisyphbaous-DT-Project

Copy link
Copy Markdown
Contributor Author

ok,我先改一下,把这段过渡动画先去掉,折叠逻辑代码量不多,我觉得没必要去掉,然后到时候有关过渡动画的我再提一个pr,顺便到时候看看能不能做的更好一点)

移除当前真实昵称控制 PR 中的通用配置项过渡动画改动,仅保留与真实昵称开关直接相关的条件显示逻辑。这样可以降低当前 PR 的审核范围,后续如需优化配置项出现和消失体验,可单独提交 UI 优化 PR。
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Jun 11, 2026
@Sisyphbaous-DT-Project

Sisyphbaous-DT-Project commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

ok,改完了,已经把这段过渡动画从当前 PR 里去掉了,只保留条件显示/折叠逻辑。

过渡动画相关的 TransitionGroup 和 CSS 都移除了。后续如果要优化配置项出现/消失的体验,我再单独开一个 UI 优化 PR 来做。

本地重新检查过:

  • npm run typecheck 通过
  • PYTHONPATH=. pytest -s tests/unit/test_astr_main_agent.py -q 通过,99 passed
  • git diff --check 通过

恢复普通配置项容器上原本存在的 config-item 类名,避免移除过渡动画时引入无关样式差异。\n\n同时移除样式块末尾多余空行,让前端 diff 保持精简。
@Sisyphbaous-DT-Project

Copy link
Copy Markdown
Contributor Author

多删了一行,已修复

Comment thread astrbot/core/astr_main_agent.py Outdated
user_id = event.message_obj.sender.user_id
user_nickname = event.message_obj.sender.nickname
system_parts.append(f"User ID: {user_id}, Nickname: {user_nickname}")
real_nickname = (

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这一块赋值夹杂条件判断,后面判断又依赖这里的赋值。是否可以适当重构来增加可读性?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

现在这段写法确实有一点嵌套,我先去把两个开关先拆成布尔变量试试

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新提交 b722f12 把两个开关拆成更明确的布尔变量,然后拆开了最终昵称和是否追加真实昵称的中间变量,行为保持不变

将真实昵称展示和仅真实昵称两个配置拆成明确的布尔变量。\n\n拆出最终昵称和是否追加真实昵称的中间变量,降低条件判断和赋值交织带来的阅读成本,保持现有行为不变。

@Dt8333 Dt8333 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感觉仍有改进空间。

Comment thread astrbot/core/astr_main_agent.py Outdated
Comment on lines +919 to +935
real_nickname = (
_sanitize_optional_metadata_value(_get_real_sender_nickname(event))
if real_nickname_display_enabled
else None
)
resolved_nickname = display_nickname
if real_nickname_only_enabled and real_nickname is not None:
resolved_nickname = real_nickname
sanitized_user_nickname = _sanitize_metadata_value(resolved_nickname)
user_metadata = {"user_id": user_id, "nickname": sanitized_user_nickname}
should_append_real_nickname = (
real_nickname is not None
and not real_nickname_only_enabled
and real_nickname != sanitized_user_nickname
)
if should_append_real_nickname:
user_metadata["real_nickname"] = real_nickname

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我想这样会精简些?

Suggested change
real_nickname = (
_sanitize_optional_metadata_value(_get_real_sender_nickname(event))
if real_nickname_display_enabled
else None
)
resolved_nickname = display_nickname
if real_nickname_only_enabled and real_nickname is not None:
resolved_nickname = real_nickname
sanitized_user_nickname = _sanitize_metadata_value(resolved_nickname)
user_metadata = {"user_id": user_id, "nickname": sanitized_user_nickname}
should_append_real_nickname = (
real_nickname is not None
and not real_nickname_only_enabled
and real_nickname != sanitized_user_nickname
)
if should_append_real_nickname:
user_metadata["real_nickname"] = real_nickname
resolved_nickname = _sanitize_metadata_value(display_nickname)
append_real_nickname = None
if real_nickname_display_enabled:
if real_nickname := _sanitize_optional_metadata_value(_get_real_sender_nickname(event)):
if real_nickname_only_enabled:
resolved_nickname = real_nickname
elif real_nickname != resolved_nickname:
append_real_nickname = real_nickname
user_metadata = {"user_id": user_id, "nickname": resolved_nickname}
if append_real_nickname:
user_metadata["real_nickname"] = append_real_nickname

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我改进一下,先把昵称清洗了,然后再用一个单独变量表示是否需要追加真实昵称,确实会比现在更加直观一点,不过这里我不打算直接用海象运算符,主要是这段本身就是为了提升可读性,普通if写法对后续维护者会更容易读一些,我按这个思路再改一版

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不用海象运算符可以直接拆成两行。应该不算麻烦。

Comment on lines +157 to +159
def _sanitize_optional_metadata_value(value: object) -> str | None:
sanitized = _sanitize_metadata_value(value)
return sanitized or None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

>>> _sanitize_optional_metadata_value("\t").__repr__()       
'None'
>>> _sanitize_optional_metadata_value(" ").__repr__() 
'None'
>>> _sanitize_optional_metadata_value("None").__repr__()
"'None'"

输入空白字符组成的字符串会输出None.
也许有风险?我不确定会不会有什么平台允许空白字符当昵称。

或许把两个方法反一下,_sanitize_metadata_value过滤None为字符串会好些?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

输入空白字符组成的字符串会输出None.
也许有风险?我不确定会不会有什么平台允许空白字符当昵称。

我觉得不应该改。这是符合我们需求的
真实昵称是可选补充信息,如果清洗后只剩空白,就应该视为“不可用”,然后回退到群昵称。我们甚至已经有测试覆盖这个行为:test_append_system_reminders_real_only_falls_back_after_sanitizing。如果这里不返回 None,反而可能导致“仅真实昵称”模式把正常群昵称替换成空昵称,这才是风险

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好的。
我注意到你的代码路径中,原始用户群昵称也经过了sanitized_user_nickname.
如果原始昵称为空白字符的话,最终到达user_metadata.nickname的值会是一个空字符串,没有任何字符。这里会有问题吗?

@Sisyphbaous-DT-Project Sisyphbaous-DT-Project Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个问题确实存在,如果有平台可以将原始昵称和群昵称都改成空白,当前的代码状态的确也是传一个空白的昵称进去,不过这是原本就存在的一个小问题,而且当前的逻辑已经比较完备了。假设有平台能做到这样,那么原版的代码也是传一个空白昵称进去,我已经做了挺多加固了,比如说这个平台的某个用户,群昵称是正常的但是真实昵称改成了空白,当前的代码逻辑在不开启“仅使用真实昵称”的情况下也是把群昵称和真实昵称一起传进去,如果使用者开了只传真实昵称的这个功能,代码检测到某一个用户的昵称清洗完是空白,也会fallback到群昵称传进去,针对群昵称和原始昵称都是空白的状态,我目前还没有想到十分完美的解决方案,以后可能可以开个新的PR来单独讨论和优化一下

先清洗展示昵称,再通过 append_real_nickname 单独表示是否追加真实昵称。\n\n避免使用海象运算符,保持可读性,同时延续真实昵称为空时回退到展示昵称的行为。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend area:webui The bug / feature is about webui(dashboard) of astrbot. feature:persona The bug / feature is about astrbot AI persona system (system prompt) size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants